# -*- coding: utf-8 *-*
'''
@author: Geraldo Oliveira
@date: 27 de maio de 2013
@obs: 
Permite criar um objecto com determinado 
numero de valores de forma aleatoria.
Objecto esse que sera sujeito ao metodo
de ordenacao selecionado
''' 

from time import clock
from random import uniform
import matplotlib.pyplot as plt
import math

class Experiment(object):
	'''
	- Gera a lista com valores aleatorios;
	- Recolhe diversa informacao sobre os 
	  tempos de execucao de cada ordenacao;
	- Exporta para ficheiro:
		- Numero de elementos ordenados;
		- Tempo medio da sua ordenacao;
		- Variancia da ordenacao;
		- Erro da medicao;
		- Tempo total da experiencia;
		- Numero de repeticoes executadas;
		- Valores calculados;
	'''
	def __init__(self, classeOrdenacao, nome_exp, n_repeticoes):
		''' 
		Recebe as opcoes selecionadas no menu principal
		e inicia as variaveis de instancia que permitiram
		conduzir a experiencia.

		classeOrdenacao	->	Recebe qual o algoritmo a 
							utilizar 
		exp_name 		->	Nome da experiencia que sera
							utilizado como nome do ficheiro
							com dados gerados sobre a mesma
		nRuns			->	Numero de repeticoes efecuadas 
							para cada uma das listas

		'''
		self.classe = classeOrdenacao
		self.nome_exp = nome_exp
		self.n_repeticoes = n_repeticoes
	
	def media(self, tempos):
		'''
		Calcula a media dos tempos

		Recebe:
			tempos	-> lista com os tempos
		
		Devolve:
			valor da media
		'''
		return reduce(lambda x, y: x + y, tempos) / len(tempos)

	def variancia(self, tempos, media):
		'''
		Calcula a variancia entre os valores
		dos tempos e da media
		
		Recebe:
			tempos	-> lista com os tempos
			media 	-> valor da media
		
		Devolve:
			valor da variancia
		'''
		return reduce(lambda x, y: x + (y-media)**2, [0] + tempos) / len(tempos)
	
	def erro(self, variancia, media):
		'''
		Calcula o erro relativo entre a variancia
		e o valor da media
		
		Recebe:
			Variancia 	-> valor da variancia
			media 		-> valor da media
		
		Devolve:
			valor do erro relativo
		'''
		return (math.sqrt(variancia) / media) * 100

	def exporta_dados(self, nome_fich_grafico, escala, tempos_calculados, \
					 fator_correlacao, valores_esperados):
		'''
		Exporta os dados da experiencia para um ficheiro
		
		Recebe:
			nome_fich_grafico 	-> nome do ficheiro a criar
			escala 				-> valor dos n (escala)
			tempos_calculados	-> valores dos tempos calculados
			fator_correlacao	-> factor do fator de correlacao
			valores_esperados	-> valores estimados
		'''
		try:
			fich_grafico = file(nome_fich_grafico, "w")
			try:
				for n in xrange(0, len(escala)):
					fich_grafico.write("{0} {1:.5f} {2:.5f}\n"\
						              .format(escala[n], tempos_calculados[n] * fator_correlacao, \
						              valores_esperados[n]))
			finally:
				fich_grafico.close()
				print "\n\nFicheiro exportado para a pasta actual!"
		except IOError:
			print "Erro na criação do ficheiro!"
		
	def runExperience(self):
		''' 
		Executa a experiencia utilizando os parametros
		solicitados. 
		'''
		# cria ficheiro onde serao exportados os
		# os dados relativos a experiencia 

		nome_fich_exper = "dados/" + self.nome_exp + ".exp"
		try:
			fich_experienca = file(nome_fich_exper, "w")
			fich_experienca.write("elementos\t\tmedia\t\t\tvariancia\t\terro relativo\n")
		except IOError:
			print "Erro na criação do ficheiro!"

		tempo_inicial = clock()
		
		# gera lista com os valores de n
		# que sera tambem a escala do grafico

		num_elementos = [1000] + range(5000, 105000, 5000)
		dados = []
		for n in num_elementos:
			tempos = []
			for k in xrange(self.n_repeticoes):

				# gera lista com os valores aleatorios
				# utilizados em cada calculo

				lista = [uniform(0.0,1.0) for k in xrange(n)]
				ordenacao = self.classe(lista)
				t_inicial = clock()
				ordenacao.run()
				t_final = clock()
				tempos.append(t_final - t_inicial)
			media = self.media(tempos)
			variancia = self.variancia(tempos, media)
			erro = self.erro(variancia, media)
			dados.append((n, media, variancia))
			fich_experienca.write("{0}\t\t\t{1:.5f}\t\t{2:.5f}\t\t{3:.2f}%\n"\
				                 .format(n, media, variancia, erro))
			
			# permite verificar dados em tempo real
			# gerados no decorer de cada experiencia

			print "\nn:     {0}".format(n)
			print "media: {0:.5f}".format(media)
			print "var:   {0:.5f}".format(variancia)
			print "erro:  {0:.2f}% ".format(erro)
		escala = [n for n, media, variancia in dados]
		tempos_calculados = [media for n, media, variancia in dados]
		ct = 15e6

		# Calculo dos valores esperados.
		# obj.o() -> retorna factor de crescimento

		tempos_esperados = [ordenacao.o(n) / ct for n, media, var in dados]
		fator_correlacao = (tempos_esperados[-1] / tempos_calculados[-1])

		# gera ficheiro para exportar os dados relativos
		# aos tempos calculados
		
		nome_fich_grafico = "dados/" + ordenacao.nome() + ".dat"
		self.exporta_dados(nome_fich_grafico, escala, tempos_calculados, \
			               fator_correlacao, tempos_esperados)
		tempo_final = clock()
		tempo_total = tempo_final - tempo_inicial
		fich_experienca.write("\nTempo total (s): {0}\n\nRepeticoes: {1}\n\n X = {2}"\
							  .format(tempo_total, \
							  self.n_repeticoes, escala))
		fich_experienca.close()
